/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.flopbox.server;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RemoteObject;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author lasse
 */
public class FlopBoxServer {

    // Static objects, describes the connection
    public static String HOST = "158.38.43.32";
    public static String NAME = "flopBoxServer";
    public static int PORT = 1337;
    
    // Add another place later
    public static String FLOPBOX_PATH = System.getProperty("user.home") + File.separator + "flopBox" + File.separator;
    
    public static DatabaseConnector db;

    public static class FileServer extends UnicastRemoteObject implements RemoteFlopboxServer {
        // List of allowed transfers        
        public FileServer() throws RemoteException {
        }
        private static HashMap<Integer, FileInfo> uploadReady = new HashMap<>();
        private static HashMap<Integer, FileInfo> downloadReady = new HashMap<>();
        private static HashMap<Integer, Long> downloadOffset = new HashMap<>();
        
        
        
        private static int currFileId = 0;

        @Override
        public void sendString(String s) throws RemoteException {
            System.out.println("Sent String: " + s);                   
        }
        //Checks if the file allready exists
        //If not, allow update
        //Else if modified Date from f is older then existing do the following
            //If file is deleted on client, delete on server and return ACKNOWLAGE
            //Else delete currentFile, and allowUppload
        

        @Override
        public int initUpload(FileInfo f) throws RemoteException {
            System.out.println("initUpload()");
            // Check if file exists on the server
            List<FileInfo> filesOnServer=db.getAllFiles();
            int index=filesOnServer.indexOf(f);
            if(index>-1){
                FileInfo fServer=filesOnServer.get(index);
                //Checks if the FileInfo from client is newer than the one on server
                if(f.getModified().after(fServer.getModified())){
                    //Deletes the old file on server and allows file upload
                    deleteFile(f.getFilename());
                    db.updateFileInfo(f);
                    //Deletes file on server if client has deleted the file, and updates the database
                    if(f.isDeleted()){
                        return RemoteFlopboxServer.DELETE_ACKNOWLEDGE;
                    }
                }
                else{
                    return RemoteFlopboxServer.TRANSFER_DENIED;
                }
            }
            else{
                // Nothing
            }
            //Allows upload if file does not exist on server, or is newer
            uploadReady.put(++currFileId, f);
            return currFileId; 
        }
        
        //Deletes file from server
        private void deleteFile(String filename){
            try{
                File fileToDelete=new File(FLOPBOX_PATH + File.separator + filename);
                fileToDelete.delete();
            }
            catch(Exception e){
                System.out.println("File "+filename+" could not be deleted");
            }
        }
        
        
        @Override
        public void doneUploadingFile(int id) throws RemoteException {
            // Insert into db
            FileInfo f = uploadReady.get(id);
            db.insertFileInfo(f);
            
            // Remove from local array
            uploadReady.remove(id);
            System.out.println("doneUploadingFile(): id: "+ id );
        }
        private void printFileList(){
            List<FileInfo> fileList=this.getFilesModified(new ArrayList<FileInfo>());
            for(FileInfo fi:fileList){
                System.out.println("Filename "+fi.getFilename()+":LastModified "+fi.getModified()+":isDeleted "+fi.isDeleted());
            }
        }
        

        @Override
        public void uploadFile(int id, byte[] ar) throws RemoteException {
            String res = "";
            for (int i = 0; i < ar.length; i++) {
                res += ar[i];
            }

            if (uploadReady.containsKey(id)) {
                String filename = uploadReady.get(id).getFilename();

                File f = new File(FLOPBOX_PATH + File.separator + filename);
                FileOutputStream fos = null;
                FileChannel channel = null;
                FileLock lock = null;
                do {
                    try {
                        fos = new FileOutputStream(f, true);
                        channel = fos.getChannel();
                        lock = channel.tryLock();
                        if (lock != null) {
                            DataOutputStream dis = new DataOutputStream(fos);
                            try {
                                dis.write(ar);
                                dis.flush();
                            } catch (IOException ex) {
                                Logger.getLogger(FlopBoxServer.class.getName()).log(Level.SEVERE, null, ex);
                            } finally {
                                lock.release();
                            }
                        }
                    } catch (Exception e) {
                    } finally {
                        if (fos != null) {
                            try {
                                fos.close();
                            } catch (IOException ex) {
                                Logger.getLogger(FlopBoxServer.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }

                        if (channel != null) {
                            try {
                                channel.close();
                            } catch (IOException ex) {
                                Logger.getLogger(FlopBoxServer.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }

                        if (lock != null && lock.isValid()) {
                            try {
                                lock.release();
                            } catch (IOException ex) {
                                Logger.getLogger(FlopBoxServer.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                    }
                } while (fos == null);
            }
        }

        private boolean isFileReady(File f) {
            int sleepTime = 1000;
            FileLock lock;
            while (!canOpenFile(f)) {
                try {
                    Thread.sleep(sleepTime);
                    sleepTime *= 2;
                } catch (InterruptedException ex) {
                    Logger.getLogger(FlopBoxServer.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            return true;
        }

        private boolean canOpenFile(File f) {
            boolean canOpen = false;
            try (FileInputStream fin = new FileInputStream(f)) {
                canOpen = true;
            } catch (Exception e) {
                canOpen = false;
            }
            return canOpen;
        }

        // TODO
        @Override
        public ArrayList<FileInfo> getFilesModified(ArrayList<FileInfo> metaFiles) {
            try {
                //Construct a list of FileInfo objects from the server.
                ArrayList<FileInfo> list=db.getAllFiles();
                java.util.Collections.sort(list);
                for(FileInfo fi:metaFiles){
                    int index=java.util.Collections.binarySearch(list, fi);
                    //If the server has one of the files listed from the client,       
                    if(index>=0){
                        FileInfo fic=list.get(index);
                        //the server will remove FileInfo fomr the returned list
                        //if modified date is equal
                        if(fic.getModified().equals(fi.getModified())){
                           list.remove(index); 
                        }
                    }
                    //If the FileInfo does not exist on server, it will be added to 
                    //the returned list
                    else{
                        list.add(fi);
                    }
                }
                return list;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        public int initDownload(FileInfo f) throws RemoteException {
            List<FileInfo> serverFiles=db.getAllFiles();
            System.out.println("initDownload(): Recieved FileInfo: "+f.getFilename());
            for (FileInfo fi : serverFiles) {
                if (f.getFilename().equals(f.getFilename())) {
                    downloadReady.put(++currFileId, f);
                    downloadOffset.put(currFileId, new Long(0));
                    return currFileId;
                }
            }
            
            return RemoteFlopboxServer.TRANSFER_DENIED;
        }

        @Override
        public byte[] downloadFile(int id) throws RemoteException {
            //Checks if the download has been approved
            FileInfo downloadInfo=downloadReady.get(new Integer(id));
            if(downloadInfo!=null){
                try{
                    //Gets the spesified file for download service
                    File f = new File(FLOPBOX_PATH+File.separator+downloadInfo.getFilename());
                    //Checks if file is ready
                    if(isFileReady(f)) {
                        //Prepers to read from file with random access
                        RandomAccessFile rf=new RandomAccessFile(f, "r");
                        Long offset=downloadOffset.get(new Integer(id));
                        //Sets poiter to last stored index
                        rf.seek(offset.longValue());
                        byte[] buf = new byte[1024];
                        int numBytes=rf.read(buf);
                        if (isFileReady(f) && numBytes != -1) {
                            //Updates the byteIndex for next call to this method
                            if(numBytes<1024){
                                downloadOffset.remove(id);
                                downloadReady.remove(id);
                            }
                            else{
                                downloadOffset.put(id, new Long(offset.longValue()+1024));
                            }
                            
                            return buf;
                        }
                    }
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
            return null;
        }

        
    }
    public static void main(String[] args) throws Exception {

        RemoteFlopboxServer server = new FileServer();
        Registry registry = LocateRegistry.createRegistry(PORT);
        registry.rebind(NAME, server);

        System.out.println("Server ready");
        
        db = new DatabaseConnector();

    }
}
